//
//  APIHeartRateImp+Analyzer.swift
//  APIHeartRate
//
//  Created by Tek on 2023/3/13.
//

import CoreBluetooth
import Foundation

extension APIHeartRateImp: HRRemotePeripheralDelegate, HRRemotePeerDelegate {
    func remotePeripheralIsReady(_ remotePeripheral: BleDevice) {
        getMacAddress()
        observers.forEach({ _, delegateApi in
            delegateApi.bleConnectStatus(status: .connected, device: remotePeripheral) })
    }

    func remotePeripheral(_ remotePeripheral: BleDevice, didUpdateName name: String) {}

    func remotePeer(characteristic: CBCharacteristic, _ remotePeer: HRRemotePeer, didSendArbitraryData data: Data) {
        guard let cmdData = data.first, let remote = remotePeripheral else {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
            return
        }
        loggerInternal.d(info: "receivedRawData(data: \(dataToHex(dataBuffer: data)))")
        switch characteristic.uuid {
        case HRServiceConst.batteryCharacteristic:
            observers.forEach({ _, delegateApi in
                delegateApi.devicePower(power: String(cmdData), device: remote) })
        case HRServiceConst.macCharacteristic:
            if data.count < 6 {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
                return
            }
            let macStr = String(format: "%02X:%02X:%02X:%02X:%02X:%02X", data[0], data[1], data[2], data[3], data[4], data[5])
            if remotePeripheral?.macAddress.count == 0 {
                remotePeripheral?.macAddress = macStr
            } else {
                observers.forEach({ _, delegateApi in
                    delegateApi.privateMacAddress(mac: macStr, device: remote) })
            }

        case HRServiceConst.manuCharacteristic:
            observers.forEach({ _, delegateApi in
                delegateApi.deviceManufacturerName(manufacturerName: String(data: data, encoding: .ascii) ?? "-", device: remote) })
        case HRServiceConst.modelCharacteristic:
            observers.forEach({ _, delegateApi in
                delegateApi.deviceModelString(modelString: String(data: data, encoding: .utf8) ?? "-", device: remote) })
        case HRServiceConst.hardwareCharacteristic:
            observers.forEach({ _, delegateApi in
                delegateApi.deviceHardware(version: String(data: data, encoding: .utf8) ?? "-", device: remote) })
        case HRServiceConst.firmwareCharacteristic:
            let versionFirm = String(data: data, encoding: .utf8) ?? "-"
            isLegacyVersion = checkIfLeagacyVersion(version: versionFirm)
            observers.forEach({ _, delegateApi in
                delegateApi.deviceFirmware(version: versionFirm, device: remote) })
        case HRServiceConst.softwareCharacteristic:
            observers.forEach({ _, delegateApi in
                delegateApi.deviceSoftware(version: String(data: data, encoding: .utf8) ?? "", device: remote) })
        case HRServiceConst.sysIDCharacteristic:
            observers.forEach({ _, delegateApi in
                delegateApi.deviceSystemData(systemData: data, device: remote) })
        case HRServiceConst.heartNotifyCharacteristic:
            if data.count < 2 {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
                return
            }
            observers.forEach({ _, delegateApi in
                delegateApi.armBandRealTimeHeartRate(hRInfo: HRInfo(rate: Int(data[1])), device: remote) })
        case HRServiceConst.notifyCharacteristic:
            mapNotifyChar(cmdData: cmdData, data: data, remote: remote)
        case HRServiceConst.otaUpdateCharUUID:
            HROTAManager.share.didUpdateValue(for: data)
        default:
            break
        }
    }

    func mapNotifyChar(cmdData: UInt8, data: Data, remote: BleDevice) {
        switch cmdData {
        case HRServiceConst.cmdVibrate:
            observers.forEach({ _, delegateApi in
                delegateApi.armBandMaxHeartRateUpdated() })
            break
        case HRServiceConst.cmdSyncTime:
            observers.forEach({ _, delegateApi in
                delegateApi.armBandSystemTimeUpdated() })
            break
        case HRServiceConst.cmdSerial:
            if data.count > 7 {
                observers.forEach({ _, delegateApi in
                    delegateApi.deviceSerialNumber(serialNumer: String(data: data.dropFirst(1), encoding: .utf8) ?? "-", device: remote) })
            } else {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
            }
            break
        case HRServiceConst.cmdStep:
            if data.count > 2 {
                observers.forEach({ _, delegateApi in
                    let v = Int(data[1] | (data[2] << 8) | (data[2] << 16) | (data[2] << 24))
                    delegateApi.armBandStepFrequency(frequencyDic: ["value": String(v)], device: remote) })
            } else {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
            }
            break
        case HRServiceConst.cmdOxygen:
            if data.count > 1 {
                observers.forEach({ _, delegateApi in
                    delegateApi.armBandBloodOxygen(num: Int(data[1]), device: remote) })
            } else {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
            }
            break
        case HRServiceConst.cmdPlay:
            observers.forEach({ _, delegateApi in
                delegateApi.armBandPlayStatusChange() })
        case HRServiceConst.cmdReset:
            if isClickReset {
                isClickReset = false
                observers.forEach({ _, delegateApi in
                    delegateApi.armBandResetFactory(isTimeOut:false) })
            }
            
        case HRServiceConst.cmdUnbind:
            observers.forEach({ _, delegateApi in
                delegateApi.armBandUnbind() })
        case HRServiceConst.cmdMeasure:
            observers.forEach({ _, delegateApi in
                delegateApi.heartRateInMeasuring() })
        case HRServiceConst.cmdAlgoOut:
            observers.forEach({ _, delegateApi in
                delegateApi.exitedSleepAlgorithm() })
        case HRServiceConst.cmdDoNotTurb:
            var sta = false
            if data.count > 1 {
                sta = data[1] == 1
            }
            observers.forEach({ _, delegateApi in
                delegateApi.doNotDisturbTimeInfo(enabled: sta)
            })
        case 0x16:
            handleCursorPacketOne(data)
        case 0x17:
            handleCursorPacketTwo(data)
        case 0x40:
            var sta = false
            if data.count > 1 {
                sta = data[1] == 1
            }
            observers.forEach({ _, delegateApi in
                delegateApi.lightStatusChanged(on: sta)
            })
        case 0x42:
            if data.count > 1 {
                if data[1] == 0 {
                    HROTAManager.share.abortOTA()
                    observers.forEach({ _, delegateApi in
                        delegateApi.bleOtaStauts(status: .failed, progress: 0)
                    })
                }
            }
        case 0x43:
            if data.count > 1, let modeValue =  HeartRateMode(rawValue: UInt8(data[1]))  {
                observers.forEach({ _, delegateApi in
                    delegateApi.heartRateModeChange(mode: modeValue)})
            }
        case 0x44:
            var sta = false
            if data.count > 1 {
                sta = data[1] == 1
            }
            observers.forEach({ _, delegateApi in
                delegateApi.gymnasticModeChanged(on: sta)
            })
        case 0x45:
            if (data.count > 1 && Int(data[1]) == 1) {
                // return empty data
                responseWithEmpty()
            }
        case 0x46:
            ackToLength(sig: 0x4a)
        case 0x47:
            if (data.count > 1) {
                observers.forEach { _, delegateApi in
                    delegateApi.heartRateMockStatus(on: Int(data[1]) == 1)
                }
            }
        case 0x48:
            if (data.count > 1) {
                observers.forEach { _, delegateApi in
                    delegateApi.wearingStatus(status: (Int(data[1]) == 1) ? .overTime : .loosen)
                }
            }
        case 0x49:
            if (data.count > 1) {
                let sType = ScenarioType.mapTypeFrom(value: data[1])
                observers.forEach { _, delegateApi in
                    delegateApi.scenarioChanged(type: sType)
                }
            }
        case 0x4a:
            if(data.count > 19) {
                let sn = String(bytes: Data(data[1..<17]), encoding: .ascii)
                deviceInfos["serialNo"] = sn
                deviceInfos["dailyNum"] = Int(data[17])
                deviceInfos["sleepNum"] = Int(data[18])
                deviceInfos["sportNum"] = Int(data[19])
            }
            ackToLength(sig: 0x4b)
        case 0x4b:
            if(data.count > 1) {
                let len = Int(data[1])
                let verFirm = String(bytes: Data(data[2..<len+2]), encoding: .ascii)
                deviceInfos["firmwareVer"] = verFirm
            }
            ackToLength(sig: 0x4c)
        case 0x4c:
            if(data.count > 1) {
                let len = Int(data[1])
                let verFirm = String(bytes: Data(data[2..<len+2]), encoding: .ascii)
                deviceInfos["hardVer"] = verFirm
            }
            ackToLength(sig: 0x4d)
            
        case 0x4d:
            if (data.count > 14) {
                let timeStamp = Int(Int32(data[1]) | Int32(data[2]) << 8 | Int32(data[3]) << 16 | Int32(data[4]) << 24)
                deviceInfos["timeStamp"] = timeStamp
                deviceInfos["doNotDisturbOn"] = Int(data[5]) == 1
                deviceInfos["startHour"] = Int(data[6])
                deviceInfos["startMin"] = Int(data[7])
                deviceInfos["endHour"] = Int(data[8])
                deviceInfos["endMin"] = Int(data[9])
                deviceInfos["lowArea"] = Int(data[10])
                deviceInfos["highArea"] = Int(data[11])
                deviceInfos["dailyRemain"] = fetchTwoByteIntValue(data, 12)
                deviceInfos["sportRemain"] = fetchTwoByteIntValue(data, 14)
                
                observers.forEach { _, delegateApi in
                    delegateApi.deviceInformation(info: deviceInfos)
                }
            }
        case HRServiceConst.cmdBattery:
            var state = BatteryStatus.normal
            let v = data.count > 1 ? data[1] : 4
            if v == 1 { state = .charging }
            else if v == 2 { state = .full }
            else if v == 4 { state = .unknown }
            observers.forEach({ _, delegateApi in
                delegateApi.batteryStatus(state: state) })
        case HRServiceConst.cmdMac:
            if data.count < 6 {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
                return
            }
            let macStr = String(format: "%02X:%02X:%02X:%02X:%02X:%02X", data[1], data[2], data[3], data[4], data[5], data[6])
            remotePeripheral?.macAddress = macStr
            observers.forEach({ _, delegateApi in
                delegateApi.privateMacAddress(mac: macStr, device: remote) })
        case HRServiceConst.cmdMode, HRServiceConst.cmdSetMode:
            if data.count < 2 {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
                return
            }
            let md: DeviceMode = data[1] == 1 ? .sport : .daily
            var ty = ModeType.other
            if data.count > 3 {
                switch data[2] {
                case 0x01:
                    ty = .autoIn
                case 0x02:
                    ty = .autoOut
                default:
                    ty = .other
                }
            }
            observers.forEach({ _, delegateApi in
                delegateApi.armBandModeChanged(mode: md, type: ty) })
        case HRServiceConst.cmdModeDetail:
            if data.count < 12 {
                observers.forEach({ _, delegateApi in
                    delegateApi.bleCommonError(error: BleCommonError.receivedWrongData) })
                return
            }
            let dict = [
                "current": Int(data[1]),
                "type": Int(data[2]),
                "level": Int(data[3]),
                "daily": Int(data[4]),
                "low": Int(data[5]),
                "heavy": Int(data[6]),
                "sp1": Int(data[7]),
                "sp2": Int(data[8]),
                "sp3": Int(data[9]),
                "dl1": Int(data[10]),
                "dl2": Int(data[11]),
                "dl3": Int(data[12])]
            observers.forEach({ _, delegateApi in
                delegateApi.sportModeDetailInfo(info: dict, isBusy: false)})
        case HRServiceConst.cmdPressure:
            observers.forEach({ _, delegateApi in
                delegateApi.armBandRealTimePressure(pressure: Int(data[1]), device: remote) })
        case HRServiceConst.cmdCurrent:
            if data.count < 4 {
                return
            }
            let currentStamp = Double(Int32(data[1]) | Int32(data[2]) << 8 | Int32(data[3]) << 16 | Int32(data[4]) << 24)
            observers.forEach({ _, delegateApi in
                delegateApi.currentBandTime(timeNow: currentStamp) })
        case HRServiceConst.cmdDataDayStart:
            let total = Int(Int32(data[1]) | Int32(data[2]) << 8 | Int32(data[3]) << 16 | Int32(data[4]) << 24)
            dataMgt.total = total
            ackToLength(sig: HRServiceConst.cmdDataDayStart)
            break
        case HRServiceConst.cmdDataDayContent:
            dataMgt.updateData(data: data)
            if dataMgt.page < dataMgt.total {
                ackToLength(sig: HRServiceConst.cmdDataDayContent)
            }
        case HRServiceConst.cmdDataDayCheck:
            
            if (showRawData) {
                let logStr = dataMgt.getRawData()
                loggerInternal.d(info: "raw: \(logStr)")
                observers.forEach({ _, delegateApi in delegateApi.rawDataLog(type:dataType, log: logStr)})
            }
            
            switch dataType {
            case .heartRate:
                let info = dataMgt.getSummaryHeartRate()
                observers.forEach({ _, delegateApi in delegateApi.summaryHeartRateInfo(info: info, isBusy: false) })
                break
            case .steps:
                let info = dataMgt.getStepSummary()
                observers.forEach({ _, delegateApi in delegateApi.summaryStepsInfo(info: info, isBusy: false) })
                break
            case .calorie:
                let info = dataMgt.getSummaryCalorie()
                observers.forEach({ _, delegateApi in delegateApi.summaryCalorieInfo(info: info, isBusy: false) })
                break
            case .pressure:
                let info = dataMgt.getSummaryPressureIndex()
                observers.forEach({ _, delegateApi in delegateApi.summaryPressureIndexInfo(info: info, isBusy: false) })
                break
            case .oxygen:
                let info = dataMgt.getSummaryBloodOxygen()
                observers.forEach({ _, delegateApi in delegateApi.summaryBloodOxygenInfo(info: info, isBusy: false) })
                break
            default:
                break
            }
            break

        case HRServiceConst.cmdDataDailyStart:
            let total = Int(Int32(data[1]) | Int32(data[2]) << 8 | Int32(data[3]) << 16 | Int32(data[4]) << 24)
            dataMgt.total = total
            ackToLength(sig: HRServiceConst.cmdDataDailyStart)
        case HRServiceConst.cmdDataDailyContent:
            dataMgt.updateData(data: data)
            if dataMgt.page < dataMgt.total {
                ackToLength(sig: HRServiceConst.cmdDataDailyContent)
            }
        case HRServiceConst.cmdDataDailyCheck:
            let info = dataMgt.getMotionData()
            if (showRawData) {
                let logStr = dataMgt.getRawData()
                loggerInternal.d(info: "raw: \(logStr)")
                observers.forEach({ _, delegateApi in delegateApi.rawDataLog(type: FetchDataType.motionDaily,log: logStr)})
            }
            observers.forEach({ _, delegateApi in delegateApi.motionDataInfo(info: info, isSport: false, isBusy: false) })
        case HRServiceConst.cmdDataSportStart:
            let total = Int(Int32(data[1]) | Int32(data[2]) << 8 | Int32(data[3]) << 16 | Int32(data[4]) << 24)
            dataMgt.total = total
            ackToLength(sig: HRServiceConst.cmdDataSportStart)
        case HRServiceConst.cmdDataSportContent:
            dataMgt.updateData(data: data)
            if dataMgt.page < dataMgt.total {
                ackToLength(sig: HRServiceConst.cmdDataSportContent)
            }
        case HRServiceConst.cmdDataSportCheck:
            let info = dataMgt.getMotionData()
            
            if (showRawData) {
                let logStr = dataMgt.getRawData()
                loggerInternal.d(info: "raw: \(logStr)")
                observers.forEach({ _, delegateApi in delegateApi.rawDataLog(type: .motionSport,log: logStr)})
            }
            
            observers.forEach({ _, delegateApi in delegateApi.motionDataInfo(info: info, isSport: true, isBusy: false) })
        case HRServiceConst.cmdDataSleepStart:
            let total = Int(Int32(data[1]) | Int32(data[2]) << 8 | Int32(data[3]) << 16 | Int32(data[4]) << 24)
            dataMgt.total = total
            ackToLength(sig: HRServiceConst.cmdDataSleepStart)
        case HRServiceConst.cmdDataSleepContent:
            dataMgt.updateData(data: data)
            if dataMgt.page < dataMgt.total {
                ackToLength(sig: HRServiceConst.cmdDataSleepContent)
            }
        case HRServiceConst.cmdDataSleepCheck:
            let info = dataMgt.getSummarySleep()
            
            if (showRawData) {
                let logStr = dataMgt.getRawData()
                loggerInternal.d(info: "raw: \(logStr)")
                observers.forEach({ _, delegateApi in delegateApi.rawDataLog(type: .sleep,log: logStr)})
            }
            
            observers.forEach({ _, delegateApi in
                delegateApi.summarySleepInfo(info: info, isBusy: false)
            })
        case HRServiceConst.cmdDataExcerciseStart:
            let total = Int(Int32(data[1]) | Int32(data[2]) << 8 | Int32(data[3]) << 16 | Int32(data[4]) << 24)
            dataMgt.total = total
            ackToLength(sig: HRServiceConst.cmdDataExcerciseStart)
        case HRServiceConst.cmdDataExcerciseContent:
            dataMgt.updateData(data: data)
            if dataMgt.page < dataMgt.total {
                ackToLength(sig: HRServiceConst.cmdDataExcerciseContent)
            }
        case HRServiceConst.cmdDataExcerciseCheck:
            let info = dataMgt.getExerciseData()
            
            if (showRawData) {
                let logStr = dataMgt.getRawData()
                loggerInternal.d(info: "raw: \(logStr)")
                observers.forEach({ _, delegateApi in delegateApi.rawDataLog(type: .excercise,log: logStr)})
            }
            
            observers.forEach({ _, delegateApi in delegateApi.summaryExerciseInfo(info: info, isBusy: false) })
        case HRServiceConst.cmdDaliyAck:
            observers.forEach({ _, delegateApi in
                delegateApi.motionDataSeqUpdated(isSport: false) })
        case HRServiceConst.cmdSportAck:
            observers.forEach({ _, delegateApi in
                delegateApi.motionDataSeqUpdated(isSport: true) })
        default:
            break
        }
    }

    func remotePeerDidWriteData() {
        HROTAManager.share.didWriteValue()
    }
               
    private func handleCursorPacketOne(_ data: Data) {
        cursorInfos.removeAll()
        if (data.count > 18) {
            cursorInfos["flag"] = Int(data[1])
            cursorInfos["days"] = Int(data[2])
            cursorInfos["sportCursor"] = fetchTwoByteIntValue(data,3)
            cursorInfos["sportSub"] = fetchTwoByteIntValue(data,5)
            cursorInfos["sportRemain"] = fetchTwoByteIntValue(data,7)
            cursorInfos["sentSCursor"] = fetchTwoByteIntValue(data,9)
            cursorInfos["sentSSub"] = fetchTwoByteIntValue(data,11)
            cursorInfos["dayCursor"] = fetchTwoByteIntValue(data,13)
            cursorInfos["daySub"] = fetchTwoByteIntValue(data,15)
            cursorInfos["dayRemain"] = fetchTwoByteIntValue(data,17)
        }
        writeData(Data([0x17]))
    }
    
    private func handleCursorPacketTwo(_ data: Data) {
        if (data.count > 6) {
            cursorInfos["sentDCursor"] = fetchTwoByteIntValue(data, 1)
            cursorInfos["sentDSub"] = fetchTwoByteIntValue(data, 3)
            cursorInfos["sleepAll"] = Int(data[5])
            cursorInfos["sportAll"] = Int(data[6])
        }
        
        observers.forEach { _, delegateApi in
            delegateApi.cursorInformation(info: cursorInfos)
        }
    }
    
    private func fetchTwoByteIntValue(_ data: Data, _ postion: Int) -> Int {
        return postion + 1 >= data.count ? 0 : (Int(data[postion + 1]) << 8 | Int(data[postion]))
    }
    
    private func responseWithEmpty() {
        switch dataType {
        case .heartRate:
            observers.forEach({ _, delegateApi in delegateApi.summaryHeartRateInfo(info: [[:]] ,isBusy: true) })
        case .steps:
            observers.forEach({ _, delegateApi in delegateApi.summaryStepsInfo(info: [[:]],isBusy: true) })
        case .calorie:
            observers.forEach({ _, delegateApi in delegateApi.summaryCalorieInfo(info: [[:]],isBusy: true) })
        case .pressure:
            observers.forEach({ _, delegateApi in delegateApi.summaryPressureIndexInfo(info: [[:]],isBusy: true) })
        case .oxygen:
            observers.forEach({ _, delegateApi in delegateApi.summaryBloodOxygenInfo(info: [[:]],isBusy: true) })
        case .motionDaily:
            observers.forEach({ _, delegateApi in delegateApi.motionDataInfo(info: [[:]], isSport: false,isBusy: true) })
        case .motionSport:
            observers.forEach({ _, delegateApi in delegateApi.motionDataInfo(info: [[:]], isSport: true,isBusy: true) })
        case .excercise:
            observers.forEach({ _, delegateApi in delegateApi.summaryExerciseInfo(info: [[:]],isBusy: true) })
        case .sleep:
            observers.forEach({ _, delegateApi in delegateApi.summarySleepInfo(info: [[:]],isBusy: true) })
        }
    }
    
    public func checkIfLeagacyVersion(version: String) -> Bool {
        if version.isEmpty || version.lowercased().starts(with: "v") {
          return true
        }
        let version1 = APIHeartRateImp.versionToCompare.shortVersion
        let version2 = version.shortVersion
        
        if version1 == version2 {
            return false
        }
        
        let version1Array = version1.split(separator: ".")
        let version2Array = version2.split(separator: ".")
        
        var diff = 0
        var index = 0
        let minLen = min(version1Array.count, version2Array.count)
        
        while (index < minLen) {
            let a = Int(version1Array[index]) ?? 0
            let b = Int(version2Array[index]) ?? 0
            diff = a - b
            if a - b == 0 {
                index += 1
                continue
            }
            break
        }
        
        if (diff == 0) {
            for vStr in version1Array {
                if let v = Int(vStr), v > 0 {
                    return true
                }
            }
            for vStr in version2Array {
                if let v = Int(vStr), v > 0 {
                    return false
                }
            }
        }
        return diff > 0
    }
}

internal extension String {
    var shortVersion: String {
        if self.lowercased().starts(with: "t") || self.lowercased().starts(with: "g") {
            return String(self.dropFirst(1)).trimmingCharacters(in: .whitespacesAndNewlines)
        }
        return self.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}
